/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 castle_gate.cpp

	$Header: /heroes4/castle_gate.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "castle_gate.h"

#include "battlefield.h"
#include "building_traits.h"
#include "combat_object_model_cache.h"
#include "combat_object_type.h"
#include "combat_reader.h"
#include "compound_object_model.h"
#include "external_string.h"
#include "format_string.h"
#include "pixel_24.h"
#include "sound.h"
#include "sound_cache.h"
#include "sound_configurations.h"
#include "stationary_combat_object.h"
#include "town.h"
#include "town_image_level.h"
#include "town_type.h"

// strengths of castle gates
namespace
{
	int const k_gate_hits[k_town_image_count]		  = { 0, 50, 100, 150 };
	int const k_gate_damage_limit[k_town_image_count] = { 0, 50, 50, 50 };
	int const k_gate_health_states = 4;
};

// --------------------------------------------------------------
// get appropriate model of gate
// --------------------------------------------------------------
static t_combat_object_model_cache get_gate_cache( t_town_type type, t_town_image_level level,
												   int health, double scale )
{
	static char const* k_health_keyword[k_gate_health_states] =
	{ "destroyed", "heavy_damage", "light_damage", "intact" };

	std::string name;

	assert( health >= 0 && health < k_town_image_count );
	if (type == k_town_none)
		name = "garrison.gate.";
	else
	{
		name = "castle.";
		name += k_town_keyword[type];
		name += ".";
		name += get_town_image_keyword( level );
		name += ".gate.";
	}
	name += k_health_keyword[health];

	return get_combat_object_model_cache( name, scale );
}

t_castle_gate::t_castle_gate( t_battlefield& battlefield ) : m_battlefield( battlefield )
{
	m_town_type = k_town_none;
	m_hit_points = 0;
	m_animation = k_gate_animation_none;
}

t_castle_gate::~t_castle_gate()
{
}

// --------------------------------------------------------------
// get castle gate sound
// --------------------------------------------------------------
namespace
{
	class t_gate_sound_set
	{
	public:
		t_gate_sound_set( std::string const& keyword );

		void play( t_town_type town_type ) const;
	protected:
		t_sound_cache m_caches[k_town_type_count + 1];
	};
};

t_gate_sound_set::t_gate_sound_set( std::string const& keyword )
{
	std::string text;
	int			i;

	text = "castle.garrison.";
	text += keyword;
	m_caches[0] = t_sound_cache( text );
	for (i = 0; i < k_town_type_count; ++i)
	{
		text = "castle.";
		text += k_town_keyword[i];
		text += ".";
		text += keyword;
		m_caches[i + 1] = t_sound_cache( text );
	}
}

void t_gate_sound_set::play( t_town_type town_type ) const
{
	t_sound_ptr sound;

	sound = m_caches[town_type + 1].get();
	if (sound != 0)
		sound->play( get_sound_volume() );
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
t_castle_gate::t_castle_gate( t_battlefield& battlefield,
							  t_combat_object_model_cache const& cache, t_town const* town )
             : t_attackable_obstacle( cache, 100 ), m_battlefield( battlefield )
{
	if (town == 0)
		m_town_type = k_town_none;
	else
		m_town_type = town->get_type();
	m_castle_level = battlefield.get_castle_level();
	m_hit_points = k_gate_hits[m_castle_level];
	m_animation = k_gate_animation_none;
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
void t_castle_gate::apply_damage( t_battlefield& battlefield, bool show_death )
{
	int limit;
	int health;
	int old_health;

	limit = k_gate_damage_limit[m_castle_level];
	if (limit > m_hit_points - m_wounds)
		limit = m_hit_points - m_wounds;
	if (m_new_damage > limit)
		m_new_damage = limit;

	if (m_new_damage == 0)
		return;

	std::string text = format_string( "-%i", m_new_damage );

	old_health = ((m_hit_points - m_wounds) * (k_gate_health_states - 1) + m_hit_points - 1) / m_hit_points;
	m_wounds += m_new_damage;
	m_new_damage = 0;
	health = ((m_hit_points - m_wounds) * (k_gate_health_states - 1) + m_hit_points - 1) / m_hit_points;
	battlefield.add_text( *m_center_segment, text, t_pixel_24(255,255,255) );
	if (health == old_health)
		return;
	change_model( get_gate_cache( m_town_type, m_castle_level, health, 
								  battlefield.get_model_scale() ) );
	if (health == 0)
		start_animation( battlefield, k_gate_animation_destroy, t_handler() );
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
void t_castle_gate::start_animation( t_battlefield& battlefield, t_gate_animation animation,
									 t_handler handler )
{
	static t_gate_sound_set const k_open_sounds( "open" );

	m_animation = animation;
	m_end_handler = handler;
	if (m_animation == k_gate_animation_close)
		set_underlay( battlefield, false );
	enable_animation( true );
	if (m_animation != k_gate_animation_destroy)
		k_open_sounds.play( m_town_type );
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
bool t_castle_gate::belongs_to( bool defender ) const
{
	return defender;
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
bool t_castle_gate::controlled_by( bool defender ) const
{
	return defender;
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
void t_castle_gate::on_idle()
{
	int i;
	int frame;

	if (!m_animating)
	{ 
		suspend_idle_processing();
		return;
	}
	frame = m_segments[0]->get_frame();
	if (m_animation == k_gate_animation_close)
	{
		if (--frame <= 0)
		{
			frame = 0;
			enable_animation( false );
			m_end_handler();
		}
	}
	else
	{
		int frame_count = m_model->get_frame_count();

		if (++frame >= frame_count)
		{
			frame = frame_count - 1;
			enable_animation( false );
			if (m_animation == k_gate_animation_open)
				set_underlay( m_battlefield, true );
			m_end_handler();
		}
	}

	i = m_segments.size();
	while (i--)
	{
		m_segments[i]->set_frame( frame );
	}
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
void t_castle_gate::change_model( t_combat_object_model_cache const& cache )
{
	t_compound_object_ptr ref = this;

	remove( m_battlefield );
	m_segments.clear();
	m_model = cache.get();
	m_model_name = cache.get_name();
	place( m_battlefield, m_position );

	int i;

	for (i = 0; i < m_segments.size(); ++i)
		m_battlefield.add_gate( m_segments[i] );
	enable_animation( false );
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
void t_castle_gate::place( t_battlefield& battlefield, t_map_point_2d const& point )
{
	m_position = point;
	t_attackable_obstacle::place( battlefield, point );
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
t_map_rect_2d t_castle_gate::get_open_rect() const
{
	t_map_rect_2d result;

	result.top_left = m_position >> k_battlefield_subcell_shift;
	result.bottom_right.column = result.left() + m_model->get_footprint_size().column;
	result.bottom_right.row = result.top() + 7; // embedded constant, rough area covered by
												// open gate
	return result;
}

// --------------------------------------------------------------
// castle gate object
// --------------------------------------------------------------
bool t_castle_gate::is_open() const
{
	if (m_wounds >= m_hit_points)
		return true;

	int frame;

	frame = m_segments[0]->get_frame();
	return (frame == m_model->get_frame_count() - 1);
}

bool t_castle_gate::is_gate() const
{
	return true;
}

t_combat_object_type t_castle_gate::get_object_type() const
{
	return k_combat_object_type_gate;
}

static int const k_current_version = 0;

bool t_castle_gate::read( t_combat_reader& reader )
{
	std::streambuf& stream = reader.get_stream();
	int				version = get<t_uint16>( stream );

	if (version < 0 || version > k_current_version )
		return false;
	if (!t_attackable_obstacle::read( reader ))
		return false;

	m_castle_level = t_town_image_level( get<t_int8>( stream ));
	m_town_type = t_town_type( get<t_int8>( stream ) );
	::read( stream, &m_position, sizeof( m_position ));
	m_hit_points = k_gate_hits[m_castle_level];
	return true;
}

bool t_castle_gate::write( t_combat_writer& writer ) const
{
	std::streambuf& stream = writer.get_stream();

	put<t_uint16>( stream, k_current_version );
	if (!t_attackable_obstacle::write( writer ))
		return false;
	put<t_int8>( stream, m_castle_level );
	put<t_int8>( stream, m_town_type );
	::write( stream, &m_position, sizeof( m_position ));
	return true;
}

void t_castle_gate::remove( t_battlefield& battlefield )
{
	if (m_removed)
		return;

	// copy objects to reference list before deleting, to ensure they stay around at
	// least for the duration of this function.
	t_compound_object_ptr	ref = this;
	int						i;
	t_stationary_combat_object_list ref_list;

	for (i = 0; i < m_segments.size(); ++i)
		ref_list.push_back( m_segments[i] );
	t_attackable_obstacle::remove( battlefield );

	t_stationary_combat_object_list::iterator index;

	for (index = ref_list.begin(); index != ref_list.end(); ++index)
	{
		battlefield.remove_gate( *index );
	}
}

void t_castle_gate::flinch( t_combat_creature const& attacker, 
							t_combat_action_message const & message )
{
	static t_gate_sound_set k_hit_sound( "flinch" );
	static t_gate_sound_set k_destroy_sound( "die" );

	if (get_new_damage() >= get_total_hits())
		k_destroy_sound.play( m_town_type );
	else
		k_hit_sound.play( m_town_type );
}

static t_external_string const k_text_gate( "gate.misc" );

std::string t_castle_gate::get_object_name() const
{
	return k_text_gate;
}
